//
//  PhotoStackViewController.mm
//  Memento 2.0
//
//  Created by Matt Toth on 10-07-05.
//  Copyright 2010 Team Memento. All rights reserved.
// 
//  Audio portions adapted from SpeakHereController.mm; see http://developer.apple.com/iphone/library/samplecode/SpeakHere/Listings/Classes_SpeakHereController_mm.html#//apple_ref/doc/uid/DTS40007802-Classes_SpeakHereController_mm-DontLinkElementID_20 
//
//  10-08-26: Route audio through headphones, or speaker if none present.  Deal more gracefully with input changes (i.e., mic disconnects).  (C. Maddocks)
//  10-08-04: Fix bug with photo collection choking on tutorial stack on second and subsequent loads  (C. Maddocks)
//  10-08-04: Fix shake handling bug  (C. Maddocks)
//  10-08-03: Fix bug that puts record button in invalid state when recording fails  (C. Maddocks)
//  10-08-03: Force sort after taking picture.  Tweak error / alert messages.  (C. Maddocks)
//  10-08-02: Fix bug causing EXC_BAD_ACCESS when starting up application and location manager not yet ready  (C. Maddocks)
//  10-07-31: Add settings panel  (C. Maddocks)
//  10-07-30: Ensure playback/camera/next/prev/etc. buttons are disabled when recording; stop playback if navigating to a new photo  (C. Maddocks)
//  10-07-30: Swap direction of memories, so that left-most image is most recent / nearest  (C. Maddocks)
//  10-07-30: Resolve low-memory view reinitialization issue  (C. Maddocks)
//	10-07-26: Add IBActions for moveLast: and moveFirst: .  Change "Thanks" to "OK" in alert views.  Update XIB for bigger buttons / better usability.  (C. Maddocks)
//  10-07-21: Bump version in header to 2.0.  (C. Maddocks)
//  10-07-20: Use horizontal gestures to better match buttons  (C. Maddocks)
//  10-07-19: Add saveData:, loadData:, and code in awakeFromNib: and viewDidLoad: to implement photo collection serialization  (C. Maddocks)
//  10-07-18: Move image rotation code to photo save to speed up swiping.  (C. Maddocks)
//  10-07-18: Performance fixes to image taking code.  Change image file format to JPEG -- much faster on iOS.  (C. Maddocks)
//  10-07-13: Fix memory leak in photo collection.  (C. Maddocks)
//  10-07-08: Disable record/play buttons when no memory is present.  (C. Maddocks)
//  10-07-08: Integrate audio code with photo stack controller.  Fix memory bugs found during testing.  (C. Maddocks)
//  10-07-07: Fix rotation issue with Memento camera (upright photos only)   (C. Maddocks)
//  10-07-07: Integrate camera code with photo stack controller.  (C. Maddocks)
//  10-07-06: Add alert view if running from simulator. Reorganise file. Integrate with PhotoCollection using key-value observing. (C. Maddocks)
//  10-07-06: Add photo storage code (M. Toth)

#import "PhotoStackViewController.h"
#import "SettingsViewController.h"

@implementation PhotoStackViewController

@synthesize player;
@synthesize recorder;

@synthesize imageView;
@synthesize btn_record;
@synthesize btn_play;
@synthesize btn_photo;
@synthesize fileDescription;
@synthesize lvlMeter_in;
@synthesize playbackWasInterrupted;
@synthesize photoCollection;
@synthesize testButton;
@synthesize locationManager;
@synthesize startingPoint;
@synthesize sortMethod;

#pragma mark -
#pragma mark Recording support functions (from Apple)

// Support function from Apple for working with low-level audio recording libraries using Cocoa
char *OSTypeToStr(char *buf, OSType t)
{
	char *p = buf;
	char str[4], *q = str;
	*(UInt32 *)str = CFSwapInt32(t);
	for (int i = 0; i < 4; ++i) {
		if (isprint(*q) && *q != '\\')
			*p++ = *q++;
		else {
			sprintf(p, "\\x%02x", *q++);
			p += 4;
		}
	}
	*p = '\0';
	return buf;
}

// Support function from Apple for working with low-level audio recording libraries using Cocoa
-(void)setFileDescriptionForFormat: (CAStreamBasicDescription)format withName:(NSString*)name
{
	char buf[5];
	const char *dataFormat = OSTypeToStr(buf, format.mFormatID);
	NSString* description = [[NSString alloc] initWithFormat:@"(%d ch. %s @ %g Hz)", format.NumberChannels(), dataFormat, format.mSampleRate, nil];
	fileDescription.text = description;
	[description release];	
}

#pragma mark -
#pragma mark Playback routines

-(void)stopPlayQueue
{
	player->StopQueue();
	[lvlMeter_in setAq: nil];
	btn_record.enabled = YES;
}

-(void)pausePlayQueue
{
	player->PauseQueue();
	playbackWasPaused = YES;
}

- (void)stopRecord
{
	// Disconnect our level meter from the audio queue
	[lvlMeter_in setAq: nil];
	
	if (recorder->IsRunning()) {
		recorder->StopRecord();		
	}

	Memory *memory = [photoCollection currentMemory];
	memory.audioFile = [memory generateFileNameWithExt:@"caf"];
	
	// Set the button's state back to "record"
UIImage *buttonRecord = [UIImage imageNamed:@"record.png"];					  
	[btn_record setImage:buttonRecord 
						  forState:UIControlStateNormal];
	
	// Re-enable buttons
	btn_play.enabled		= YES;
	btn_photo.enabled		= YES;
	btnLeft.enabled			= YES;
	btnRight.enabled		= YES;
	btnLeftmost.enabled		= YES;
	btnRightmost.enabled	= YES;
	
	// Reload the audio queue, so Memento is aware
	[self loadAudioQueue];
}

// This method is invoked when the user indicates they want to take a photo.
- (IBAction)photo:(id)sender
{
	// First check that there is a camera on the device.
	// This allows you to run in the iPhone Simulator or on an iPod Touch without crashing.
	
	if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){
	
		// Whenever the user launches the camera button, try and force an updated GPS fix if last reading was 3 minutes or less
		// (note that locationManager should be aware of changes greater than 100m by subscription anyway)
		NSDate *locationTimestamp = locationManager.location.timestamp;
		if (locationTimestamp < [[NSDate date] dateByAddingTimeInterval:-180]) {
			[locationManager stopUpdatingLocation];
			[locationManager startUpdatingLocation];		
		}		
		
		// Launch iOS camera component.  If user takes a picture, image picker will call delegate method
		// imagePickerController:didFinishPickingMediaWithInfo:
		UIImagePickerController *picker = [[UIImagePickerController alloc] init];
		picker.delegate = self;
		picker.sourceType = UIImagePickerControllerSourceTypeCamera;

		[self presentModalViewController:picker animated:YES];
		[picker release];
		
	} else {
		
		// Gracefully notify the user if no camera is present
		
		UIAlertView *alert = [[UIAlertView alloc] 
							  initWithTitle:@"No camera found" 
							  message:@"An iPhone is required to take photos using Memento." 
							  delegate:nil 
							  cancelButtonTitle:@"Thanks"
							  otherButtonTitles:nil];
		[alert show];
		[alert release];
	}
}

// This method is invoked when the user indicates they want to play the audio recording (if any).
- (IBAction)play:(id)sender
{
	if (player->IsRunning())
	{
		if (playbackWasPaused) {
			OSStatus result = player->StartQueue(true);
			if (result == noErr)
				[[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueResumed" object:self];
		}
		else
			[self stopPlayQueue];
	}
	else
	{		
		OSStatus result = player->StartQueue(false);
		if (result == noErr)
			[[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueResumed" object:self];
	}
}

// This method is invoked when the user indicates they want to record audio.
- (IBAction)record:(id)sender
{
	// Detect if microphone present
	UInt32 micConnected = 0, propertySize = sizeof(micConnected);
    AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &propertySize, &micConnected);
    if (!micConnected) {
		// Gracefully notify the user if no camera is present
		
		UIAlertView *alert = [[UIAlertView alloc] 
							  initWithTitle:@"No microphone found" 
							  message:@"Please attach your iPod microphone to record audio notes using Memento." 
							  delegate:nil 
							  cancelButtonTitle:@"Thanks"
							  otherButtonTitles:nil];
		[alert show];
		[alert release];
		
		return;
	}
	
	
	if (recorder->IsRunning()) // If we are currently recording, stop and save the file.
	{
		[self stopRecord];
	}
	else // If we're not recording, start.
	{
		// Set the button's state to "stop"
		UIImage *buttonRStop = [UIImage imageNamed:@"stop.png"];					  
		[btn_record setImage:buttonRStop
					forState:UIControlStateNormal];	
		
		// Disable next, prev buttons
		btn_play.enabled = NO;	
		btn_photo.enabled = NO;
		btnLeft.enabled = NO;
		btnRight.enabled = NO;
		btnLeftmost.enabled = NO;
		btnRightmost.enabled = NO;
		
		// Determine the filepath
		Memory *memory = [photoCollection currentMemory];
		NSString *memoryAudioFile = [memory generateFileNameWithExt:@"caf"];
		NSString *memoryAudioPath = [[photoCollection collectionDataPath] stringByAppendingPathComponent:memoryAudioFile];		
	
		// Start the recorder
		recorder->StartRecord(CFStringRef(memoryAudioPath));
		//recorder->StartRecord(CFSTR("recordedFile.caf"));
		
		[self setFileDescriptionForFormat:recorder->DataFormat() withName:@"Recorded File"];
		
		// Hook the level meter up to the Audio Queue for the recorder
		[lvlMeter_in setAq: recorder->Queue()];
		
		// In case recording failed for some reason, make sure we go back to non-recording state
		if (!recorder->IsRunning()) // If we are currently recording, stop and save the file.
		{
			UIAlertView *alert = [[UIAlertView alloc] 
								  initWithTitle:@"Could not record" 
								  message:@"Something went wrong while trying to record.  Please make sure you have a microphone attached, and try again." 
								  delegate:nil 
								  cancelButtonTitle:@"Thanks"
								  otherButtonTitles:nil];
			[alert show];
			[alert release];
					
			[self stopRecord];
			
			return;

		}
		
	}	
}

- (void)loadAudioQueue 
{
	player->DisposeQueue(true);  // dispose the previous playback queue
	
	if (photoCollection.currentMemory.audioFile != nil) {
		NSString *path = [photoCollection collectionDataPath];

		// now create a new queue for the recorded file
		CFStringRef playbackFilePath = (CFStringRef)[path stringByAppendingPathComponent:photoCollection.currentMemory.audioFile];
		
		player->CreateQueueForFile(playbackFilePath);	
		
		if (!recorder->IsRunning()) btn_play.enabled = YES;
	} else {
		btn_play.enabled = NO;
	}
	
	btn_record.enabled = YES;
}

#pragma mark -
#pragma mark Photo navigation methods

- (void)moveLeft {
	if (recorder->IsRunning()) return; // Don't move when we're recording
	
	NSString *proximityText = (self.sortMethod == kSortByLocation ? @"nearest" : @"most recent");
	
	// Note that moving "left" moves "next" in photoCollection, because we want the most recent/nearest photos on the left
	if (![photoCollection moveNext]) {
		UIAlertView *alert = [[UIAlertView alloc] 
							  initWithTitle:@"Beginning of photo stack" 
							  message:[NSString stringWithFormat:@"You've reached the %@ photo in the stack.", proximityText]
							  delegate:nil 
							  cancelButtonTitle:@"OK" 
							  otherButtonTitles:nil];
		[alert show];
		[alert release];
	} 	
	
}

- (void)moveRight {
	if (recorder->IsRunning()) return; // Don't move when we're recording
	
	NSString *proximityText = (self.sortMethod == kSortByLocation ? @"farthest" : @"oldest");
	
	// Note that moving "right" moves "previous" in photoCollection, because we want the most recent/nearest photos on the left
	if (![photoCollection movePrev]) {
		UIAlertView *alert = [[UIAlertView alloc] 
							  initWithTitle:@"End of photo stack" 
							  message:[NSString stringWithFormat:@"You've reached the %@ photo in the stack.", proximityText]
							  delegate:nil 
							  cancelButtonTitle:@"OK" 
							  otherButtonTitles:nil];
		[alert show];
		[alert release];
	} 	 
}

- (void)moveLeftmost {
	if (recorder->IsRunning()) return; // Don't move when we're recording
	
	// Note that moving "left" moves "next" in photoCollection, because we want the most recent/nearest photos on the left
	[photoCollection moveLast];
}

- (void)moveRightmost {
	if (recorder->IsRunning()) return; // Don't move when we're recording
	
	// Note that moving "right" moves "previous" in photoCollection, because we want the most recent/nearest photos on the left
	[photoCollection moveFirst];
}


// PhotoStackViewController observes photoCollection.currentMemory.  Through the magic of KVO, this method will be called whenever
// photoCollection.currentMemory is changed, allowing us to update the UI.
- (void)observeValueForKeyPath:(NSString *)keyPath 
					  ofObject:(id)object 
						change:(NSDictionary *)change 
					   context:(void *)context {
	if ((object == photoCollection) && (keyPath = @"currentMemory")) {
		// Stop playback
		if (player->IsRunning()) {  // Stop playback
			[self stopPlayQueue];
		}		
		
		Memory *memory = [photoCollection currentMemory];
		NSDate *date = [memory dateTime];
		
		// Format time
		NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
		[dateFormatter setDateFormat:@"HH:mm"];
		NSString *timeString = [dateFormatter stringFromDate:date];
		
		// Format date
		[dateFormatter setDateFormat:@"EEEE, MMMM d, y"];
		NSString *dateString = [dateFormatter stringFromDate:date];
		[dateFormatter release];
		
		// Format location
		// Calculate distance in real-time rather than using value cached in Memory class (latter is for sorting purposes)
		NSString *distanceString = @"Location unavailable";
		if (memory.location.latitude != 0 && memory.location.longitude != 0 && locationManager.location != nil) {
			[memory updateDistanceFromLocation:locationManager.location];
			//locationString = [[NSString alloc] initWithFormat:@"%g, %g", memory.location.latitude, memory.location.longitude];
			distanceString = (memory.distance < 300) ? @"Close by" : [[NSString alloc] initWithFormat:@"%01.1f km from here", memory.distance/1000];
		}
	
		// Update UI labels	
		[locationLabel setText:distanceString];				
		[timeLabel setText:timeString];
		[dateLabel setText:dateString];
		
//		[locationString release];
		[distanceString release];
		
		// Load image
		if (memory.image != nil) {  
			imageView.image = memory.image;
		} else if (memory.imageFile != nil) {  // Lazy load allows us to store the UIImage directly for recently-accessed memories, and in the filesystem for non-accessed memories
			NSString *path = [photoCollection collectionDataPath];
			UIImage *image = [[UIImage alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:memory.imageFile]];		
			//memory.image = image;
			imageView.image = image;
				
			[image release];
		} else {
			imageView.image = nil;
		}
		
		// Load audio
		[self loadAudioQueue];  // We need to call this both here, and after recording.
	}
}

- (IBAction)toggleSettings {
	SettingsViewController *svc = [[SettingsViewController alloc] initWithPhotoCollection:self.photoCollection];
	[self presentModalViewController:svc animated:YES];
	[svc release];
}


#pragma mark -
#pragma mark Touch detection methods

// These methods together detect vertical "swipe" events, for browsing between memories.
// See LaMarche - Beginning iPhone 3 Development - ch. 13.5
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    gestureStartPoint = [touch locationInView:self.view];
	swipeHandled = NO;
	
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
	
    if (swipeHandled) return;
	
	UITouch *touch = [touches anyObject];
    CGPoint currentPosition = [touch locationInView:self.view];
	
    CGFloat deltaX = fabsf(gestureStartPoint.x - currentPosition.x);
    CGFloat deltaY = fabsf(gestureStartPoint.y - currentPosition.y);
	
	// Detect horizontal swipes only
	if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance) {
		if (gestureStartPoint.x > currentPosition.x) {	// Swipe right-to-left, i.e., move right
			[self moveRight];
		} else {		// Swipe left-to-right, i.e., move left
			[self moveLeft];
		}
		swipeHandled = YES;
	}

}

#pragma mark -
#pragma mark shake detection methods
- (void)accelerometer:(UIAccelerometer *)accelerometer
		didAccelerate:(UIAcceleration *)acceleration {

	
	if ((acceleration.x > kAccelerationThreshold
		|| acceleration.y > kAccelerationThreshold
		|| acceleration.z > kAccelerationThreshold) && (!shakeHandled)) {
		
		shakeHandled = YES;
		UIAlertView *alert = [[UIAlertView alloc]
							  initWithTitle:@"Delete Memory?"
							  message:@"Are you sure you want to delete this memory?"
							  delegate:self
							  cancelButtonTitle:@"Yes"
							  otherButtonTitles:@"Cancel", nil];
		[alert show];
		[alert release];
	
	}
}

//this code does not work...
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
	// the user clicked one of the OK/Cancel buttons
	if (buttonIndex == 0)
	{
		[photoCollection deleteCurrentMemory];
	}
	shakeHandled = NO;
}

#pragma mark -
#pragma mark UIImagePickerControllerDelegate methods

// This delegate method is called when user completes taking a picture through the iOS image picker.  
// Here we will create a new memory, save the image, and associate the image with the memory.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
	// Generate memory
	UIImage *newImage = [[info objectForKey:@"UIImagePickerControllerOriginalImage"] retain];
	Memory *memory = [photoCollection generateMemoryWithImage:newImage location:locationManager.location.coordinate];
	
	[picker dismissModalViewControllerAnimated:YES];
	
	NSString *memoryImageFile = [memory generateFileNameWithExt:@"jpeg"];
	NSString *memoryImagePath = [[photoCollection collectionDataPath] stringByAppendingPathComponent:memoryImageFile];
	
	// Save to disk (concurrent)
	MemorySaveOperation *saveOperation = [[MemorySaveOperation alloc] initForMemory:memory withImage:newImage toFilePath:memoryImagePath];
	[imageSaveQueue addOperation:saveOperation];
	[saveOperation release];
	[newImage release];

	[memory setImageFile:memoryImageFile];	 	
	
	// Resort if necessary
	if (sortMethod == kSortByLocation) {
		[photoCollection sortBy:kSortByLocation];
	}	
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
	[picker dismissModalViewControllerAnimated:YES];
}

#pragma mark Image helper functions



#pragma mark -
#pragma mark AudioSession listeners

// This code is from Apple's SpeakHere sample application.

void interruptionListener(	void *	inClientData,
							UInt32	inInterruptionState)
{
	PhotoStackViewController *THIS = (PhotoStackViewController*)inClientData;
	if (inInterruptionState == kAudioSessionBeginInterruption)
	{
		if (THIS->recorder->IsRunning()) {
			[THIS stopRecord];
		}
		else if (THIS->player->IsRunning()) {
			//the queue will stop itself on an interruption, we just need to update the UI
			[[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueStopped" object:THIS];
			THIS->playbackWasInterrupted = YES;
		}
	}
	else if ((inInterruptionState == kAudioSessionEndInterruption) && THIS->playbackWasInterrupted)
	{
		// we were playing back when we were interrupted, so reset and resume now
		THIS->player->StartQueue(true);
		[[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueResumed" object:THIS];
		THIS->playbackWasInterrupted = NO;
	}
}

void propListener(	void *                  inClientData,
					AudioSessionPropertyID	inID,
					UInt32                  inDataSize,
					const void *            inData)
{
	PhotoStackViewController *THIS = (PhotoStackViewController*)inClientData;
	if (inID == kAudioSessionProperty_AudioRouteChange)
	{
		CFDictionaryRef routeDictionary = (CFDictionaryRef)inData;			
		//CFShow(routeDictionary);
		CFNumberRef reason = (CFNumberRef)CFDictionaryGetValue(routeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
		SInt32 reasonVal;
		CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal);
		if (reasonVal != kAudioSessionRouteChangeReason_CategoryChange)
		{
			/*CFStringRef oldRoute = (CFStringRef)CFDictionaryGetValue(routeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_OldRoute));
			if (oldRoute)	
			{
				printf("old route:\n");
				CFShow(oldRoute);
			}
			else 
				printf("ERROR GETTING OLD AUDIO ROUTE!\n");
			
			CFStringRef newRoute;
			UInt32 size; size = sizeof(CFStringRef);
			OSStatus error = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &size, &newRoute);
			if (error) printf("ERROR GETTING NEW AUDIO ROUTE! %d\n", error);
			else
			{
				printf("new route:\n");
				CFShow(newRoute);
			}*/

			if (reasonVal == kAudioSessionRouteChangeReason_OldDeviceUnavailable)
			{			
				if (THIS->player->IsRunning()) {
					[THIS pausePlayQueue];
					[[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueStopped" object:THIS];
				}		
			}

			// stop the queue if we had a non-policy route change
			if (THIS->recorder->IsRunning()) {
				[THIS stopRecord];
			}
		}	
	}
	else if (inID == kAudioSessionProperty_AudioInputAvailable)
	{
		if (inDataSize == sizeof(UInt32)) {
			UInt32 isAvailable = *(UInt32*)inData;

			UInt32 category = (isAvailable ? kAudioSessionCategory_PlayAndRecord : kAudioSessionCategory_MediaPlayback);
			int error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
			if (error) printf("couldn't set audio category!");
			
		}
	}
}
			

# pragma mark Notification routines
- (void)playbackQueueStopped:(NSNotification *)note
{
	UIImage *buttonPlay = [UIImage imageNamed:@"play.png"];					  
	[btn_play setImage:buttonPlay 
				forState:UIControlStateNormal];
	[lvlMeter_in setAq: nil];
	btn_record.enabled = YES;
}

- (void)playbackQueueResumed:(NSNotification *)note
{
	UIImage *buttonPStop = [UIImage imageNamed:@"stop.png"];					  
	[btn_play setImage:buttonPStop 
				forState:UIControlStateNormal];
	btn_record.enabled = NO;
	[lvlMeter_in setAq: player->Queue()];
}




#pragma mark -
#pragma mark Initialization and cleanup

// Initialization routine when view controller is first initialized from the XIB.
// Here we will set up our recording and playing classes; register our observer requests, and perform other misc. setup.
- (void)awakeFromNib
{		
	// Allocate our singleton instance for the recorder & player object
	recorder = new AQRecorder();
	player = new AQPlayer();
	
	// Initialize PhotoCollection, and set up key-value observing so we are notified when the current photo changes
	//photoCollection = [[PhotoCollection alloc] init];
	[self loadData];
	[photoCollection addObserver:self 
					  forKeyPath:@"currentMemory" 
						 options:NSKeyValueObservingOptionNew 
						 context:nil];
	
	swipeHandled = YES;
	
	// Initialize image picker and image save operation queue
	imageSaveQueue = [[NSOperationQueue alloc] init];
	
	OSStatus error = AudioSessionInitialize(NULL, NULL, interruptionListener, self);
	if (error) printf("ERROR INITIALIZING AUDIO SESSION! %d\n", (int)error);
	else 
	{
		// Detect microphone
		UInt32 micConnected = 0, size = sizeof(micConnected);
		error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &micConnected);
		if (error) printf("couldn't detect input availability!");
		
		UInt32 category = (micConnected ? kAudioSessionCategory_PlayAndRecord : kAudioSessionCategory_MediaPlayback);
		error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
		if (error) printf("couldn't set audio category!");
					
		error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, propListener, self);
		if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
		UInt32 inputAvailable = 0;
		size = sizeof(inputAvailable);
		
		// Output to speaker
		//UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
		//AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,sizeof (audioRouteOverride),&audioRouteOverride);
		UInt32 audioRouteDefaultToSpeaker = 1; 
		AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, sizeof(audioRouteDefaultToSpeaker), &audioRouteDefaultToSpeaker); 

		// we do not want to allow recording if input is not available
		error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &inputAvailable);
		if (error) printf("ERROR GETTING INPUT AVAILABILITY! %d\n", (int)error);
		btn_record.enabled = (inputAvailable) ? YES : NO;
		
		// we also need to listen to see if input availability changes
		error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioInputAvailable, propListener, self);
		if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
		
		error = AudioSessionSetActive(true); 
		if (error) printf("AudioSessionSetActive (true) failed");
	}
	
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackQueueStopped:) name:@"playbackQueueStopped" object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackQueueResumed:) name:@"playbackQueueResumed" object:nil];
	
	UIColor *bgColor = [[UIColor alloc] initWithRed:.39 green:.44 blue:.57 alpha:.5];
	[lvlMeter_in setBackgroundColor:bgColor];
	[lvlMeter_in setBorderColor:bgColor];
	[bgColor release];
	
	// disable the play button since we have no recording to play yet
	btn_play.enabled = NO;
	playbackWasInterrupted = NO;
	playbackWasPaused = NO;
	
	//4 lines for GPS startup
	self.locationManager = [[CLLocationManager alloc] init];
	locationManager.delegate = self;
	locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
	locationManager.distanceFilter = 1000.0f;
	[locationManager startUpdatingLocation];	
}


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
// We will use this temporarily to hide our test buttons after N seconds (currently 0, can be increased during debugging)
- (void)viewDidLoad {
	NSLog(@"Initializing PhotoStack view in viewDidLoad");
		
	//3 lines for shaking code
	UIAccelerometer *accel = [UIAccelerometer sharedAccelerometer];
    accel.delegate = self;
    accel.updateInterval = kUpdateInterval;
	
	[super viewDidLoad];
	
	[self performSelector:@selector(hideTestButton)
			   withObject:nil afterDelay:0];
	btn_play.enabled = NO;
	btn_record.enabled = NO;
	
	// Fix button appearance
	[self glassifyButtons];
	
	if (photoCollection.count > 0) {
		[photoCollection moveLast];  // i.e., move to most recent / nearest photo
	} else {
		// [self resetDisplay];		
	}
		
}

- (void)glassifyButtons {
	UIImage *buttonRecordImageNormal = [UIImage imageNamed:@"redButton.png"];
	UIImage *stretchableButtonRecordImageNormal = [buttonRecordImageNormal
												   stretchableImageWithLeftCapWidth:15 topCapHeight: 0];
	[btn_record setBackgroundImage:stretchableButtonRecordImageNormal 
						  forState:UIControlStateNormal];
	
	UIImage *buttonRecordImagePressed = [UIImage imageNamed:@"whiteButton.png"];
	UIImage *stretchableButtonRecordImagePressed = [buttonRecordImagePressed
													stretchableImageWithLeftCapWidth:15 topCapHeight:0];
	
	[btn_record setBackgroundImage:stretchableButtonRecordImagePressed
						  forState:UIControlStateHighlighted];
	
	UIImage *buttonPlayImageNormal = [UIImage imageNamed:@"greenButton.png"];
	UIImage *stretchableButtonPlayImageNormal = [buttonPlayImageNormal
												 stretchableImageWithLeftCapWidth:15 topCapHeight: 0];
	[btn_play setBackgroundImage:stretchableButtonPlayImageNormal 
						forState:UIControlStateNormal];
	UIImage *buttonPlayImagePressed = [UIImage imageNamed:@"whiteButton.png"];
	UIImage *stretchableButtonPlayImagePressed = [buttonPlayImagePressed
												  stretchableImageWithLeftCapWidth:15 topCapHeight:0];
	
	[btn_play setBackgroundImage:stretchableButtonPlayImagePressed
						forState:UIControlStateHighlighted];
	
	UIImage *buttonPhotoImageNormal = [UIImage imageNamed:@"blackButton.png"];
	UIImage *stretchableButtonPhotoImageNormal = [buttonPhotoImageNormal
												  stretchableImageWithLeftCapWidth:15 topCapHeight: 0];
	[btn_photo setBackgroundImage:stretchableButtonPhotoImageNormal 
						 forState:UIControlStateNormal];
	UIImage *buttonPhotoImagePressed = [UIImage imageNamed:@"whiteButton.png"];
	UIImage *stretchableButtonPhotoImagePressed = [buttonPhotoImagePressed
												   stretchableImageWithLeftCapWidth:15 topCapHeight:0];
	
	[btn_photo setBackgroundImage:stretchableButtonPhotoImagePressed
						 forState:UIControlStateHighlighted];
	
}

- (void)resetDisplay {
	NSDate *date = [NSDate date];
	
	// Format time
	NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
	[dateFormatter setDateFormat:@"HH:mm"];
	NSString *timeString = [dateFormatter stringFromDate:date];
	
	// Format date
	[dateFormatter setDateFormat:@"EEEE, MMMM d, y"];
	NSString *dateString = [dateFormatter stringFromDate:date];
	[dateFormatter release];
	
	// Format location
	NSString *locationString = [NSString stringWithFormat:@"Photo collection is empty"];
	
	// Update UI labels	
	locationLabel.text = locationString;
	timeLabel.text = timeString;
	dateLabel.text = dateString;	
}

- (void)dealloc
{
	[locationManager release];
	[btn_record release];
	[btn_photo release];
	[btn_play release];
	[photoCollection removeObserver:self forKeyPath:@"currentMemory"];
//	[toolbar release];
	[fileDescription release];
	[lvlMeter_in release];
	[photoCollection release];
	[imageSaveQueue release];
	[startingPoint release];
	
	delete player;
	delete recorder;
	
	[super dealloc];
}
							
#pragma mark -
#pragma mark Serialization
							
- (void) loadData {
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
	NSString *documentsDirectory = [paths objectAtIndex:0];
	NSString *path = [documentsDirectory stringByAppendingPathComponent:@"catalog.plist"];
	
	NSLog (@"loading data from %@", path);
	self.photoCollection = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] retain];

	if (self.photoCollection == NULL) {
		self.photoCollection = [[PhotoCollection alloc] init];
	}
	
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	sortMethod = [(NSString *)[defaults objectForKey:kSortKey] intValue];
	
	if ([photoCollection count] > 0) {
		[photoCollection sortBy:sortMethod];
	} else {
		[self loadTutorial];
	}
}

- (void) saveData {
	if ([photoCollection count] == 0) return;
	
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
	NSString *documentsDirectory = [paths objectAtIndex:0];
	NSString *path = [documentsDirectory stringByAppendingPathComponent:@"catalog.plist"];

	NSLog (@"saving data to %@", path);
	[NSKeyedArchiver archiveRootObject:self.photoCollection toFile:path];
}

- (void)loadTutorialMemory:(NSString *)imageName {
	CLLocationCoordinate2D coord;
	coord.latitude = 0;
	coord.longitude = 0;
	NSError *err;
	
	NSString *filename = [NSString stringWithFormat:@"%@.png", imageName];
	NSString *audioFilename = [NSString stringWithFormat:@"%@.caf", imageName];
	
	NSString *target = [[photoCollection collectionDataPath] stringByAppendingPathComponent:filename];
	[[NSFileManager defaultManager] copyItemAtPath:[[NSBundle mainBundle] pathForResource:imageName ofType:@"png"]
											toPath:target
											 error:&err];	

	NSString *audioTarget = [[photoCollection collectionDataPath] stringByAppendingPathComponent:audioFilename];
	[[NSFileManager defaultManager] copyItemAtPath:[[NSBundle mainBundle] pathForResource:imageName ofType:@"caf"]
											toPath:audioTarget
											 error:&err];	
	
	[photoCollection generateMemoryWithImage:[UIImage imageWithContentsOfFile:target] location:coord];
	[[photoCollection currentMemory] setImageFile:filename];
	[[photoCollection currentMemory] setAudioFile:audioFilename];
}

- (void)loadTutorial {
	[self loadTutorialMemory:@"Tutorial6"];
	[self loadTutorialMemory:@"Tutorial5"];
	[self loadTutorialMemory:@"Tutorial4"];
	[self loadTutorialMemory:@"Tutorial3"];
	[self loadTutorialMemory:@"Tutorial2"];
	[self loadTutorialMemory:@"Tutorial1"];
}
							
#pragma mark -
#pragma mark CLLocationManagerDelegate Methods

- (void)locationManager:(CLLocationManager *)manager {
	// Resort if necessary
	if (sortMethod == kSortByLocation) {
		[photoCollection sortBy:kSortByLocation];
	}
}

- (void)locationManager:(CLLocationManager *)manager
       didFailWithError:(NSError *)error {
	
	//stop running locationmanager
	//since there was an error, we don't want to keep trying to get the location
	[locationManager stopUpdatingLocation];
	
    NSString *errorType = (error.code == kCLErrorDenied) ?
	@"Could not get location" : @"Please make sure location services are turned on, and you are connected to the internet.";
    UIAlertView *alert = [[UIAlertView alloc]
						  initWithTitle:@"Problems getting location"
						  message:errorType
						  delegate:nil
						  cancelButtonTitle:@"Okay"
						  otherButtonTitles:nil];
    [alert show];
    [alert release];
}


#pragma mark -
#pragma mark Test methods

// Creates three test memories and adds them to the photo collection.  Note these memories will not have images or audio associated with them.
- (void) setupTestData {
	NSCalendar *gregorian = [[NSCalendar alloc]
							 initWithCalendarIdentifier:NSGregorianCalendar];
	NSDateComponents *components = [[NSDateComponents alloc] init];
	[components setDay:15];
	[components setMonth:7]; // July
	[components setYear:2009];
	[components setHour:1];
	[components setMinute:5]; 
	NSDate *memoryDate = [gregorian dateFromComponents:components];

	
	[photoCollection addMemoryWithContentsOfDictionary:[NSDictionary dictionaryWithObjectsAndKeys:
														  [NSNumber numberWithInt:-3], kMemoryId,
														  [NSNull null], kImageFile,
														  [NSNull null], kAudioFile, 
														  memoryDate, kDateTime,
														  [NSNumber numberWithDouble:115.12], kLatitude,
														  [NSNumber numberWithDouble:188.63], kLongitude, 
														  nil]];	 
	[components setDay:27];
	[components setMonth:4]; //April
	[components setYear:2010];
	[components setHour:2];
	[components setMinute:6]; 
	memoryDate = [gregorian dateFromComponents:components];
	
	[photoCollection addMemoryWithContentsOfDictionary:[NSDictionary dictionaryWithObjectsAndKeys:
														[NSNumber numberWithInt:-2], kMemoryId,
														[NSNull null], kImageFile,
														[NSNull null], kAudioFile, 
														memoryDate, kDateTime,
														[NSNumber numberWithDouble:83.12], kLatitude,
														[NSNumber numberWithDouble:47.63], kLongitude, 
														nil]];	
	
	[components setDay:4];
	[components setMonth:7]; //July
	[components setYear:2010];
	[components setHour:3];
	[components setMinute:7]; 
	memoryDate = [gregorian dateFromComponents:components];
	
	[photoCollection addMemoryWithContentsOfDictionary:[NSDictionary dictionaryWithObjectsAndKeys:
														[NSNumber numberWithInt:-1], kMemoryId,
														[NSNull null], kImageFile,
														[NSNull null], kAudioFile, 
														memoryDate, kDateTime,
														[NSNumber numberWithDouble:83.12], kLatitude,
														[NSNumber numberWithDouble:47.63], kLongitude, 
														nil]];	
	
	[components release];
	[gregorian release];
		
}

- (IBAction)hideTestButton {
	[testButton removeFromSuperview];
}

@end
